#include "stdafx.h"
#include "Mesh.h"
#include "Texture.h"
#include <Utilities\Exceptions.h>

void ItemBufferMeshSubdivider::apply_rules(void) 
{ 
	Tvv4(); 
	VdE(); 
	EVc(coeffs_); 
}

void RadiosityMesh::Create(const Parameters* const parameters)
{
	this->request_face_normals();

	TriangleMesh triangleMesh;
	triangleMesh.request_face_colors();
	triangleMesh.request_face_normals();

	if (!IO::read_mesh(triangleMesh, parameters->GetMeshFilename()))
	{
		ostringstream ostr;
		ostr << "Unable to open mesh file \"" << parameters->GetMeshFilename() << "\".";
		throw FileException(ostr.str());
	}

	//Copy parameters from initialized triangle "halves" of quadrilaterals to halves without parameters
	CopyTriangleColors(triangleMesh);
	//Creates the display mesh by removing the hypotenuses from the triangle mesh
	CreateRadiosityMesh(triangleMesh);
	//Creates the item buffer mesh by subdividing the triangle mesh
	CreateItemBufferMesh(triangleMesh, parameters->GetSubdivisionLevel());
	//Calculates face area, model span distance, etc.
	FinalizeMeshes();
}

void RadiosityMesh::CopyTriangleColors(TriangleMesh& triangleMesh)
{
	TriangleMesh::FaceIter faceIter, f_end;
	TriangleMesh::FaceHalfedgeIter faceHalfedgeIter;

	//Go through each face
	for (faceIter = triangleMesh.faces_begin(), f_end = triangleMesh.faces_end(); faceIter != f_end; ++faceIter)
	{
		TriangleMesh::Color c = triangleMesh.color(faceIter.handle());
		//Find any "blank" triangles and set them to the reflectivity and normal of their opposite
		if(c[0] != 0xff && c[1] != 0xff && c[2] != 0xff)
		{
			TriangleMesh::Scalar max_l = 0;
			TriangleMesh::HalfedgeHandle halfEdge;
			for (faceHalfedgeIter = triangleMesh.fh_iter(faceIter.handle()); faceHalfedgeIter; ++faceHalfedgeIter)
			{
				//find the longest halfedge
				TriangleMesh::Scalar l = triangleMesh.calc_edge_length(faceHalfedgeIter.handle());
				if(l > max_l)
				{
					max_l = l;
					halfEdge = faceHalfedgeIter.handle();
				}
			}
			TriangleMesh::FaceHandle fh = triangleMesh.face_handle(triangleMesh.opposite_halfedge_handle(halfEdge));
			triangleMesh.set_color(faceIter.handle(), triangleMesh.color(fh));
			TriangleMesh::Normal fn = triangleMesh.normal(fh).norm() ? triangleMesh.normal(fh) : Normal(0, 0, 0);
			triangleMesh.set_normal(faceIter.handle(), fn);
		}
	}
}

void RadiosityMesh::CreateRadiosityMesh(TriangleMesh& triangleMesh)
{
	this->request_halfedge_status();
	this->request_face_status();
	this->request_edge_status();
	this->request_vertex_status();
	this->request_face_colors();

	FPropHandleT<float> tempId;
	IdentifyLongestHalfedge(*this, triangleMesh);
	ConvertTriangleMeshToPolyMesh(*this, triangleMesh, tempId);
	SetRadiosityMeshProperties(triangleMesh, tempId);

	this->release_halfedge_status();
	this->release_face_status();
	this->release_edge_status();
	this->release_vertex_status();
	this->release_face_colors();
}

void RadiosityMesh::SetRadiosityMeshProperties(TriangleMesh& triangleMesh, FPropHandleT<float>& tempId)
{
	for (FaceIter faceIter = this->faces_begin(); faceIter != this->faces_end(); ++faceIter)
		faceIter->SetFaceReflectivity(this->color(faceIter));

	//Set the triangles in the original triangle mesh to the new handle Id of their originating quad
	TriangleMesh::FaceIter triangleFaceIter = triangleMesh.faces_begin();
	for (triangleFaceIter = triangleMesh.faces_begin(); triangleFaceIter != triangleMesh.faces_end(); ++triangleFaceIter)
	{
		TriangleMesh::Normal normal = triangleMesh.normal(triangleFaceIter);
		for (FaceIter faceIter = this->faces_begin(); faceIter != this->faces_end(); ++faceIter)
		{
			if(abs(this->property(tempId, faceIter) - static_cast<int>(normal[0])) < 0.001)
			{
				triangleMesh.set_normal(triangleFaceIter, TriangleMesh::Normal(static_cast<float>(faceIter.handle().idx()), 0, 0));
				break;
			}
		}
	}

	this->remove_property(tempId);
}

void RadiosityMesh::CreateItemBufferMesh(TriangleMesh& triangleMesh, const int subdivisionLevel)
{
	ItemBufferMeshSubdivider subdivider;
	subdivider.attach(triangleMesh);
	subdivider(subdivisionLevel);
	subdivider.detach();

	_itemBufferMesh.request_halfedge_status();
	_itemBufferMesh.request_face_status();
	_itemBufferMesh.request_edge_status();
	_itemBufferMesh.request_vertex_status();
	_itemBufferMesh.request_face_colors();
	_itemBufferMesh.request_face_normals();

	FPropHandleT<float> tempId;
	IdentifyLongestHalfedge(_itemBufferMesh, triangleMesh);
	ConvertTriangleMeshToPolyMesh(_itemBufferMesh, triangleMesh, tempId);
	SetItemBufferMeshProperties();

	_itemBufferMesh.release_halfedge_status();
	_itemBufferMesh.release_face_status();
	_itemBufferMesh.release_edge_status();
	_itemBufferMesh.release_vertex_status();
	_itemBufferMesh.release_face_colors();
	_itemBufferMesh.release_face_normals();
}

void RadiosityMesh::SetItemBufferMeshProperties(void)
{
	for (ItemBufferMesh::FaceIter faceIter = _itemBufferMesh.faces_begin(); faceIter != _itemBufferMesh.faces_end(); ++faceIter)
	{
		ItemBufferMesh::Normal normal = _itemBufferMesh.normal(faceIter);
		RadiosityMesh::FaceHandle parentFace(static_cast<int>(normal[0]));
		RadiosityMesh::FaceIter parentFaceIter(*this, parentFace);
		parentFaceIter->AddChildFaceHandle(faceIter.handle());
	}

	//Sets up the item buffer mesh's texture coordinates that map to the correct places in the parent face
	for (RadiosityMesh::FaceIter radiosityFaceIter = this->faces_begin(); radiosityFaceIter != this->faces_end(); ++radiosityFaceIter)
	{
		RadiosityMesh::FaceVertexIter radiosityFaceVertexIter = this->fv_iter(radiosityFaceIter);
		Vec3f origin = this->point(radiosityFaceVertexIter);
		vector<Vec3f> faceSides(2);
		for(vector<Vec3f>::iterator iter = faceSides.begin(); iter != faceSides.end(); iter++)
		{
			RadiosityMesh::Point point1 = this->point(radiosityFaceVertexIter);
			RadiosityMesh::Point point2 = this->point(++radiosityFaceVertexIter);
			*iter = point2 - point1;
		}

		Vec2f faceSidesSquared;
		faceSidesSquared[0] = powf(faceSides[0].norm(), 2);
		faceSidesSquared[1] = powf(faceSides[1].norm(), 2);
		for (vector<ItemBufferMesh::FaceHandle>::const_iterator iter = radiosityFaceIter->GetChildFaceHandles().begin(); iter != radiosityFaceIter->GetChildFaceHandles().end(); iter++)
		{
			int i = 0;
			ItemBufferMesh::FaceIter childFaceIter(_itemBufferMesh, *iter);
			for(ItemBufferMesh::FaceVertexIter faceVertexIter = _itemBufferMesh.fv_iter(*iter); faceVertexIter; ++faceVertexIter, i++)
			{
				Vec3f item_vector = Vec3f(_itemBufferMesh.point(faceVertexIter)) - origin;
				childFaceIter->SetTexCoord(i, ItemBufferMesh::TexCoord2D(dot(faceSides[0], item_vector) / faceSidesSquared[0], dot(faceSides[1], item_vector) / faceSidesSquared[1]));
			}
		}
	}
}

void RadiosityMesh::FinalizeMeshes(void)
{
	Vec2f faceSize(0.0f, 0.0f);
	Vec2i textureSize(0, 0);
	_maxTextureSize = Vec2i(0, 0);

	//Go through each face in the mesh...
	for (RadiosityMesh::FaceIter faceIter = this->faces_begin(); faceIter != this->faces_end(); ++faceIter)
	{
		RadiosityMesh::FaceHalfedgeIter faceHalfedgeIter = this->fh_iter(faceIter);
		for(int i = 0; i < 2; ++faceHalfedgeIter, i++)
		{
			//calculate the length of two consecutive edges (width and height)
			faceSize[i] = this->calc_edge_length(faceHalfedgeIter.handle());
			textureSize[i] = static_cast<int>(max(1.0f, ceilf(faceSize[i])));
		}
		//Set the face area
		faceIter->SetFaceArea(faceSize[Width] * faceSize[Height]);

		//Set the texture's dimensions
		faceIter->SetRequestedTextureSize(textureSize);
		_maxTextureSize[Width] = max(_maxTextureSize[Width], textureSize[Width]);
		_maxTextureSize[Height] = max(_maxTextureSize[Height], textureSize[Height]);
	}

	//Compute bounding box
	Vec3f boundingBoxMin(this->point(this->fv_iter(this->faces_begin())));
	Vec3f boundingBoxMax(boundingBoxMin);

	//Go through each face in the mesh...
	for (RadiosityMesh::FaceIter faceIter = this->faces_begin(); faceIter != this->faces_end(); ++faceIter)
	{
		for(RadiosityMesh::FaceVertexIter faceVertexIter = this->fv_iter(faceIter); faceVertexIter; ++faceVertexIter)
		{
			Vec3f point = this->point(faceVertexIter);
			for(int i = 0; i < 3; i++)
			{
				if (point[i] < boundingBoxMin[i])
				{
					boundingBoxMin[i] = point[i];
				}
				if (point[i] > boundingBoxMax[i])
				{
					boundingBoxMax[i] = point[i];
				}
			}
		}
	}
	_modelSpanDistance = (boundingBoxMax - boundingBoxMin).norm();

	//Generate face occlusion queries for the entire face (used when rendering from the camera)
	for (RadiosityMesh::FaceIter faceIter = this->faces_begin(); faceIter != this->faces_end(); ++faceIter)
	{
		faceIter->GenerateUserViewOcclusionQuery();
	}

	this->GenerateFovOcclusionQueries();

	//Generate face occlusion queries for the item buffer mesh (used when rendering from the shooting face)
	for (ItemBufferMesh::FaceIter faceIter = _itemBufferMesh.faces_begin(); faceIter != _itemBufferMesh.faces_end(); ++faceIter)
	{
		faceIter->GenerateItemBufferOcclusionQuery();
	}

	_facePositions.Create(Array, GL_RGB16F_ARB, GL_RGB, GL_FLOAT, 16, 16, this->n_faces());
}

const int RadiosityMesh::GetMaxTextureSize(const int dimension) const
{
	return _maxTextureSize[dimension];
}

const float RadiosityMesh::GetModelSpanDistance(void) const
{
	return _modelSpanDistance;
}

const ItemBufferMesh& RadiosityMesh::GetItemBufferMesh(void) const
{ 
	return _itemBufferMesh; 
}

const TextureArray& RadiosityMesh::GetFacePositionsTextureVariable(void) const
{
	return _facePositions;
}

const RadiosityMesh::FaceHandle RadiosityMesh::GetFaceHandle(const GLint faceId) const
{
	return face_handle(faceId);
}

void RadiosityMesh::GenerateFovOcclusionQueries(void)
{
	_fovOcclusionQueries = new GLuint[this->n_faces()];
	for (RadiosityMesh::FaceIter faceIter = this->faces_begin(); faceIter != this->faces_end(); ++faceIter)
	{
		glGenQueries(1, &_fovOcclusionQueries[faceIter.handle().idx()]);
	}
}

const bool RadiosityMesh::GetIsVisibleFov(RadiosityMesh::FaceIter faceIter) const
{
	GLuint result;
	glGetQueryObjectuiv(_fovOcclusionQueries[faceIter.handle().idx()], GL_QUERY_RESULT, &result);
	return result > 0;
}

void RadiosityMesh::GetFovOcclusionQueryResults(GLubyte* results)
{
	int i = 0;
	for (RadiosityMesh::FaceIter faceIter = this->faces_begin(); faceIter != this->faces_end(); ++faceIter, i++)
	{
		results[i] = GetIsVisibleFov(faceIter) ? 255 : 0;
	}
}

const GLuint RadiosityMesh::GetFovOcclusionQuery(RadiosityMesh::FaceIter iter)
{
	return _fovOcclusionQueries[iter.handle().idx()];
}
